package com.pactera.asmp.server.schedule;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import com.pactera.asmp.server.common.constants.ScreenRedisKey;
import com.pactera.asmp.server.dao.TProjectMasterComputerDbcMapper;
import com.pactera.asmp.server.dao.TaskMapper;
import com.pactera.asmp.server.dao.UserProjectMapper;
import com.pactera.asmp.server.entity.DeviceExcetimeDataDTO;
import com.pactera.asmp.server.entity.UserProjectStatisticsDataDTO;
import com.pactera.asmp.server.pojo.TProjectMasterComputerDbc;
import com.pactera.asmp.server.pojo.TProjectMasterComputerDbcExample;
import com.pactera.asmp.server.utils.DateUtil;

@Service
@EnableScheduling
public class DeviceSchedule {

    private Logger logger = LoggerFactory.getLogger(DeviceSchedule.class);

    @Autowired
    private TaskMapper taskMapper;

    @Autowired
    private UserProjectMapper userProjectMapper;

    @Autowired
    private TProjectMasterComputerDbcMapper projectMasterComputerDbcMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final Long ONE_DAY_LONG = 24 * 60 * 60L;

    private static final BigDecimal ONE_HOUR = new BigDecimal(60 * 60);

    private static final DateTimeFormatter FORMAT_YYYYMM = DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_YYYYMM);

    private static final DateTimeFormatter FORMAT_YYYY_MM = DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_YYYY_MM);

    private static final DateTimeFormatter FORMAT_YYYY_MM_DD = DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_YYYY_MM_DD);

    private static final DateTimeFormatter FORMAT_YYYY_MM_DD_HH_MM_SS = DateTimeFormatter.ofPattern(DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS);

    private static final BigDecimal WORK_DAY = new BigDecimal(21.75);

    /**
     * 统计设备使用时长的数据（每天执行一次）
     */
    @Scheduled(cron = "0 1 0 * * ?")
    public void calculateDeviceUsedTime(){
        logger.info("============ calculate last day used time start ============");
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startDateTime = LocalDateTime.parse(now.plusDays(-1L).format(FORMAT_YYYY_MM_DD) + " 00:00:00", FORMAT_YYYY_MM_DD_HH_MM_SS);
        LocalDateTime endDateTime = LocalDateTime.parse(now.format(FORMAT_YYYY_MM_DD) + " 00:00:00", FORMAT_YYYY_MM_DD_HH_MM_SS);
        // 第一次计算需算出之前每天的值
        if(!redisTemplate.hasKey(ScreenRedisKey.REDIS_KEY_SCREEN_EXCETIME_BY_MASTERCOMPUTE)) {
            startDateTime = null;
        }
        logger.info("calculate startTime:{}, endTime:{}", startDateTime, endDateTime);
        List<DeviceExcetimeDataDTO> deviceExcetimeDataDTOList = taskMapper.selectExceTimeByMasterComputer(startDateTime, endDateTime);
        if(CollectionUtils.isEmpty(deviceExcetimeDataDTOList)) {
            logger.info("no summary found");
            logger.info("============ calculate last day used time end ============");
            // 当日没有执行的上位机
            final Map<Object, Object> freeMap = redisTemplate.opsForHash().entries(ScreenRedisKey.REDIS_KEY_SCREEN_FREETIME_BY_MASTERCOMPUTE);
            for(Object idObj : freeMap.keySet()) {
                Integer masterComputerId = Integer.parseInt(idObj.toString());
                long freeTime = Long.parseLong(freeMap.get(idObj).toString()) + ONE_DAY_LONG;
                redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_FREETIME_BY_MASTERCOMPUTE, String.valueOf(masterComputerId), String.valueOf(freeTime));
                Object obj = redisTemplate.opsForHash().get(ScreenRedisKey.REDIS_KEY_SCREEN_EXCETIME_BY_MASTERCOMPUTE, String.valueOf(masterComputerId));
                if(null != obj) {
                    BigDecimal rate = new BigDecimal(Long.parseLong(obj.toString())).divide(new BigDecimal(freeTime + Long.parseLong(obj.toString())), 4, BigDecimal.ROUND_HALF_UP);
                    redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_EXCERATE_BY_MASTERCOMPUTE, String.valueOf(masterComputerId), rate.toString());
                }
            }
            return;
        }
        // 获取最初的日期
        startDateTime = LocalDateTime.parse(deviceExcetimeDataDTOList.get(0).getExeStartTime().format(FORMAT_YYYY_MM_DD) + " 00:00:00", FORMAT_YYYY_MM_DD_HH_MM_SS);

        // 计算设备占用时长
        Map<Integer, List<DeviceExcetimeDataDTO>> deviceExcetimeDataDTOListMap = deviceExcetimeDataDTOList.stream().collect(Collectors.groupingBy(DeviceExcetimeDataDTO::getMasterComputerId));
        for(Integer masterComputerId : deviceExcetimeDataDTOListMap.keySet()) {
            if(CollectionUtils.isNotEmpty(deviceExcetimeDataDTOListMap.get(masterComputerId))) {
                Long exceTime = 0L;
                for(DeviceExcetimeDataDTO deviceExcetimeDataDTO : deviceExcetimeDataDTOListMap.get(masterComputerId)) {
                    if(null == deviceExcetimeDataDTO.getExeStartTime() || null == deviceExcetimeDataDTO.getExeEndTime()) {
                        continue;
                    }
                    Duration duration = Duration.between(
                            deviceExcetimeDataDTO.getExeStartTime().isBefore(startDateTime) ? startDateTime : deviceExcetimeDataDTO.getExeStartTime()
                            , deviceExcetimeDataDTO.getExeEndTime().isBefore(endDateTime) ? deviceExcetimeDataDTO.getExeEndTime() : endDateTime);
                    exceTime += duration.getSeconds();
                }
                long wholeExceTime = exceTime;
                Object obj = redisTemplate.opsForHash().get(ScreenRedisKey.REDIS_KEY_SCREEN_EXCETIME_BY_MASTERCOMPUTE, String.valueOf(masterComputerId));
                if(null != obj) {
                    wholeExceTime += Long.parseLong(obj.toString());
                }
                if(wholeExceTime == 0) {
                    continue;
                }else {
                    redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_EXCETIME_BY_MASTERCOMPUTE, String.valueOf(masterComputerId), String.valueOf(wholeExceTime));
                }

                long freeTime = 0L;
                Object objFreeTime = redisTemplate.opsForHash().get(ScreenRedisKey.REDIS_KEY_SCREEN_FREETIME_BY_MASTERCOMPUTE, String.valueOf(masterComputerId));
                if(null == objFreeTime) {
                    // 获取第一次绑定时间
                    TProjectMasterComputerDbcExample example =new TProjectMasterComputerDbcExample();
                    TProjectMasterComputerDbcExample.Criteria criteria= example.createCriteria();
                    criteria.andMasterComputerIdEqualTo(masterComputerId);
                    example.setOrderByClause("create_time");
                    List<TProjectMasterComputerDbc> projectMasterComputerDbcs = projectMasterComputerDbcMapper.selectByExample(example);
                    if(CollectionUtils.isEmpty(projectMasterComputerDbcs)) {
                        logger.error("masterComputerId:{}, have no related project.", masterComputerId);
                        continue;
                    }
                    LocalDateTime createTime = projectMasterComputerDbcs.get(0).getCreateTime();
                    Duration duration = Duration.between(createTime, endDateTime);
                    freeTime = duration.getSeconds() - wholeExceTime;
                }else {
                    freeTime = Long.parseLong(objFreeTime.toString()) + ONE_DAY_LONG - exceTime;
                }
                redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_FREETIME_BY_MASTERCOMPUTE, String.valueOf(masterComputerId), String.valueOf(freeTime));
                BigDecimal rate = new BigDecimal(wholeExceTime).divide(new BigDecimal(freeTime + wholeExceTime), 4, BigDecimal.ROUND_HALF_UP);
                redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_EXCERATE_BY_MASTERCOMPUTE, String.valueOf(masterComputerId), rate.toString());
            }
        }
        // 当日没有执行的上位机
        final Map<Object, Object> freeMap = redisTemplate.opsForHash().entries(ScreenRedisKey.REDIS_KEY_SCREEN_FREETIME_BY_MASTERCOMPUTE);
        for(Object idObj : freeMap.keySet()) {
            Integer masterComputerId = Integer.parseInt(idObj.toString());
            if(!deviceExcetimeDataDTOListMap.containsKey(masterComputerId)) {
                long freeTime = Long.parseLong(freeMap.get(idObj).toString()) + ONE_DAY_LONG;
                redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_FREETIME_BY_MASTERCOMPUTE, String.valueOf(masterComputerId), String.valueOf(freeTime));
                Object obj = redisTemplate.opsForHash().get(ScreenRedisKey.REDIS_KEY_SCREEN_EXCETIME_BY_MASTERCOMPUTE, String.valueOf(masterComputerId));
                if(null != obj) {
                    BigDecimal rate = new BigDecimal(Long.parseLong(obj.toString())).divide(new BigDecimal(freeTime + Long.parseLong(obj.toString())), 4, BigDecimal.ROUND_HALF_UP);
                    redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_EXCERATE_BY_MASTERCOMPUTE, String.valueOf(masterComputerId), rate.toString());
                }
            }
        }
        logger.info("============ calculate last day used time end ============");
    }

    /**
     * 统计夜间设备使用时长的数据（每天执行一次）
     */
    @Scheduled(cron = "0 1 0 * * ?")
    public void calculateDeviceUsedTimeByNight(){
        logger.info("============ calculate night used time start ============");
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startDateTime = LocalDateTime.parse(now.plusDays(-1L).format(FORMAT_YYYY_MM_DD).concat(" 19:00:00"), FORMAT_YYYY_MM_DD_HH_MM_SS);
        LocalDateTime endDateTime = LocalDateTime.parse(now.format(FORMAT_YYYY_MM_DD).concat(" 07:00:00"), FORMAT_YYYY_MM_DD_HH_MM_SS);
        // 第一次计算需算出之前每天的值
        if(!redisTemplate.hasKey(ScreenRedisKey.REDIS_KEY_SCREEN_EXCETIME_BYNIGHT_MASTERCOMPUTE)) {
            startDateTime = null;
        }
        logger.info("calculate startTime:{}, endTime:{}", startDateTime, endDateTime);
        List<DeviceExcetimeDataDTO> deviceExcetimeDataDTOList = taskMapper.selectExceTimeByMasterComputer(startDateTime, endDateTime);
        if(CollectionUtils.isEmpty(deviceExcetimeDataDTOList)) {
            logger.info("no summary found");
            logger.info("============ calculate night used time end ============");
            return;
        }
        Map<Integer, List<DeviceExcetimeDataDTO>> deviceExcetimeDataDTOListMap = deviceExcetimeDataDTOList.stream().collect(Collectors.groupingBy(DeviceExcetimeDataDTO::getMasterComputerId));
        // 计算设备占用时长
        for(Integer masterComputerId : deviceExcetimeDataDTOListMap.keySet()) {
            if(CollectionUtils.isNotEmpty(deviceExcetimeDataDTOListMap.get(masterComputerId))) {
                startDateTime = LocalDateTime.parse(deviceExcetimeDataDTOListMap.get(masterComputerId).get(0).getExeStartTime().format(FORMAT_YYYY_MM_DD) + " 19:00:00", FORMAT_YYYY_MM_DD_HH_MM_SS);
                Long exceTimeByNight = 0L;
                while(startDateTime.isBefore(endDateTime)) {
                    LocalDateTime nextDateTime = LocalDateTime.parse(startDateTime.plusDays(1L).format(FORMAT_YYYY_MM_DD).concat(" 07:00:00"), FORMAT_YYYY_MM_DD_HH_MM_SS);
                    for(DeviceExcetimeDataDTO deviceExcetimeDataDTO: deviceExcetimeDataDTOListMap.get(masterComputerId)) {
                        if(null == deviceExcetimeDataDTO.getExeStartTime() || null == deviceExcetimeDataDTO.getExeEndTime()) {
                            continue;
                        }
                        if(deviceExcetimeDataDTO.getExeStartTime().isBefore(nextDateTime) && deviceExcetimeDataDTO.getExeEndTime().isAfter(startDateTime)) {
                            Duration duration = Duration.between(
                                    deviceExcetimeDataDTO.getExeStartTime().isBefore(startDateTime) ? startDateTime : deviceExcetimeDataDTO.getExeStartTime()
                                    , deviceExcetimeDataDTO.getExeEndTime().isBefore(nextDateTime) ? deviceExcetimeDataDTO.getExeEndTime() : nextDateTime);
                            exceTimeByNight += duration.getSeconds();
                        }

                    }
                    startDateTime = LocalDateTime.parse(startDateTime.plusDays(1L).format(FORMAT_YYYY_MM_DD).concat(" 19:00:00"), FORMAT_YYYY_MM_DD_HH_MM_SS);
                }
                if(exceTimeByNight.longValue() > 0) {
                    Object obj = redisTemplate.opsForHash().get(ScreenRedisKey.REDIS_KEY_SCREEN_EXCETIME_BYNIGHT_MASTERCOMPUTE, String.valueOf(masterComputerId));
                    if(null != obj) {
                        exceTimeByNight += Long.parseLong(obj.toString());
                    }
                    redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_EXCETIME_BYNIGHT_MASTERCOMPUTE, String.valueOf(masterComputerId), exceTimeByNight.toString());
                }
            }
        }

        logger.info("============ calculate night used time end ============");
    }

    /**
     * 统计项目节约人力以及节约工时的数据（每月执行一次）
     */
    @Scheduled(cron = "0 0 1 1 * ?")
    public void calculateSaveTimeByMonth(){
        logger.info("============ calculate save time by month start ============");
        LocalDateTime now = LocalDateTime.now();
        // 获取上个月开始与结束日
        LocalDateTime lastMonthLastDay = LocalDateTime.parse(now.minusDays(0).format(FORMAT_YYYY_MM_DD).concat(" 23:59:59"), FORMAT_YYYY_MM_DD_HH_MM_SS);
        LocalDateTime lastMonthFirstDay = LocalDateTime.parse(lastMonthLastDay.format(FORMAT_YYYY_MM).concat("-01 00:00:00"), FORMAT_YYYY_MM_DD_HH_MM_SS);

        List<DeviceExcetimeDataDTO> dataList = taskMapper.selectExceTimeByProjectId(lastMonthFirstDay, lastMonthLastDay);
        if(CollectionUtils.isEmpty(dataList)) {
            logger.info("no data found");
            logger.info("============ calculate save time by month end ============");
            return;
        }
        Map<Integer, List<DeviceExcetimeDataDTO>> dataMap = dataList.stream().collect(Collectors.groupingBy(DeviceExcetimeDataDTO::getProjectId));
        Map<Integer, Long> projectIdExceTimeMap = new HashMap<>();
        for(Integer projectId : dataMap.keySet()) {
            Long exceTime = 0L;
            for(DeviceExcetimeDataDTO deviceExcetimeDataDTO : dataMap.get(projectId)) {
                if(null == deviceExcetimeDataDTO.getExeStartTime() || null == deviceExcetimeDataDTO.getExeEndTime()) {
                    continue;
                }
                Duration duration = Duration.between(
                        deviceExcetimeDataDTO.getExeStartTime().isBefore(lastMonthFirstDay) ? lastMonthFirstDay : deviceExcetimeDataDTO.getExeStartTime()
                        , deviceExcetimeDataDTO.getExeEndTime().isBefore(lastMonthLastDay) ? deviceExcetimeDataDTO.getExeEndTime() : lastMonthLastDay);
                exceTime += duration.getSeconds();
            }
            projectIdExceTimeMap.put(projectId, exceTime);
        }
        // 获取项目人数
        List<UserProjectStatisticsDataDTO> userProjectDTOs = userProjectMapper.summaryTestUserByProjectId();
        Map<Integer, Long> userProjectMap = userProjectDTOs.stream().collect(Collectors.toMap(UserProjectStatisticsDataDTO::getProjectId, UserProjectStatisticsDataDTO::getUserCount));

        for(Integer projectId : projectIdExceTimeMap.keySet()) {
            Long userCount = 0L;
            if(MapUtils.isNotEmpty(userProjectMap) && userProjectMap.containsKey(projectId)) {
                userCount = userProjectMap.get(projectId);
            }
            BigDecimal saveHour = new BigDecimal(projectIdExceTimeMap.get(projectId)).divide(ONE_HOUR, 2, BigDecimal.ROUND_HALF_UP)
                    // TODO 获取每个月的工作日
                    .subtract(new BigDecimal(userCount * 8).multiply(WORK_DAY));

            BigDecimal saveHumanRes = saveHour.divide(new BigDecimal(8), 2, BigDecimal.ROUND_HALF_UP);
            redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_SAVETIME_BYMONTH_PROJECT, String.valueOf(projectId),
                    saveHour.setScale(0, BigDecimal.ROUND_HALF_UP).toString() + "," + saveHumanRes.setScale(0, BigDecimal.ROUND_HALF_UP).toString());
        }
        logger.info("============ calculate save time by month end ============");
    }

    /**
     * 同比上月提示使用率（每月执行一次）
     */
    @Scheduled(cron = "0 0 1 1 * ?")
    public void calculateLastMonthIncreaseRate(){
        logger.info("============ calculate last month increase start ============");
        LocalDateTime now = LocalDateTime.now();
        // 获取上个月开始与结束日
        LocalDateTime lastMonthLastDay = LocalDateTime.parse(now.withDayOfMonth(1).format(FORMAT_YYYY_MM_DD).concat(" 00:00:00"), FORMAT_YYYY_MM_DD_HH_MM_SS);
        LocalDateTime lastMonthFirstDay = LocalDateTime.parse(now.minusMonths(1).withDayOfMonth(1).format(FORMAT_YYYY_MM_DD).concat(" 00:00:00"), FORMAT_YYYY_MM_DD_HH_MM_SS);

        List<DeviceExcetimeDataDTO> dataList = taskMapper.selectExceTimeByMasterComputer(lastMonthFirstDay, lastMonthLastDay);
        if(CollectionUtils.isEmpty(dataList)) {
            logger.info("no data found");
            logger.info("============ calculate last month increase end ============");
            return;
        }
        String lastMonthStr = lastMonthFirstDay.format(FORMAT_YYYYMM);
        // 获取上上个月的使用率
        String lastsMonthStr = lastMonthFirstDay.minusMonths(1L).format(FORMAT_YYYYMM);
        Map<Integer, List<DeviceExcetimeDataDTO>> dataMap = dataList.stream().collect(Collectors.groupingBy(DeviceExcetimeDataDTO::getMasterComputerId));
        Duration totalDuration = Duration.between(lastMonthFirstDay, lastMonthLastDay);
        long totalTime =totalDuration.getSeconds();
        for(Integer masterComputerId : dataMap.keySet()) {
            Long exceTime = 0L;
            for(DeviceExcetimeDataDTO deviceExcetimeDataDTO : dataMap.get(masterComputerId)) {
                if(null == deviceExcetimeDataDTO.getExeStartTime() || null == deviceExcetimeDataDTO.getExeEndTime()) {
                    continue;
                }
                Duration duration = Duration.between(
                        deviceExcetimeDataDTO.getExeStartTime().isBefore(lastMonthFirstDay) ? lastMonthFirstDay : deviceExcetimeDataDTO.getExeStartTime()
                        , deviceExcetimeDataDTO.getExeEndTime().isBefore(lastMonthLastDay) ? deviceExcetimeDataDTO.getExeEndTime() : lastMonthLastDay);
                exceTime += duration.getSeconds();
            }
            BigDecimal rate = new BigDecimal(exceTime).divide(new BigDecimal(totalTime), 4, BigDecimal.ROUND_HALF_UP);
            redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_MASTERCOMPUTE_EXCERATE_BY_MONTH.concat(lastMonthStr), masterComputerId.toString(), rate.toString());
            Object obj = redisTemplate.opsForHash().get(ScreenRedisKey.REDIS_KEY_SCREEN_MASTERCOMPUTE_EXCERATE_BY_MONTH.concat(lastsMonthStr), masterComputerId.toString());
            BigDecimal increaseRate = rate;
            if(obj != null) {
                increaseRate = rate.subtract(new BigDecimal(obj.toString()));
            }
            redisTemplate.opsForHash().put(ScreenRedisKey.REDIS_KEY_SCREEN_MASTERCOMPUTE_LASTMONTH_INCREASE_EXCERATE, masterComputerId.toString(), increaseRate.toString());
        }

        logger.info("============ calculate last month increase end ============");
    }
}
